using System;
using System.Drawing;
using System.Collections;
using System.Reflection;

namespace Meta
{
	public abstract class SomewhatRichTextWriter : System.IO.TextWriter {
		abstract public void WriteStringInFont(string s, Font f);
		abstract public void Write(System.Drawing.Image i);
		abstract public void UnindentEndOfBuffer();
		abstract public Graphics CreateGraphics();
		abstract public Color GetBackColor();
	}

	public interface IRichWriteable {
		void RichWrite(SomewhatRichTextWriter s);
	}

	public class Writer {
		static TypeTable discloserTable = new TypeTable();

		//static Writer() {
		//	SetDisclosure(typeof(Type), new string[] { "Name" });
		//}

		public Writer(System.IO.TextWriter o) {
			stream = o;
			isRichText = stream is SomewhatRichTextWriter;
		}
		public System.IO.TextWriter stream;
		public bool isRichText;
		public static string WriteToString(object tuple) {
			System.IO.StringWriter stream = new System.IO.StringWriter();
			new Writer(stream).Write(tuple);
			return stream.ToString();
		}
		public static string SafeWriteToString(object tuple, int maxLength) {
			//			System.Text.StringBuilder builder =new System.Text.StringBuilder(Math.Min(maxLength, Math.Max(maxLength/4,10)), maxLength);
			//			System.IO.StringWriter stream = new System.IO.StringWriter(builder);
			System.Text.StringBuilder sb = new System.Text.StringBuilder(0,maxLength);
			System.IO.StringWriter stream = new BetterStringWriter(sb);
			string result;
			try {
				int max = 50;
				new Writer(stream).Write(tuple, ref max);
				result = stream.ToString();
			} catch (ArgumentOutOfRangeException) {
				int maxL = sb.MaxCapacity-3;  // Longest it can be if we're to add an ellipsis
				if (sb.Length>=maxL)
					sb.Remove(maxL, sb.Length-maxL);
				sb.Append("...");
				result = stream.ToString();
			} catch (Exception e) {
				result = string.Format("<A {0} occured while trying to print this value>", e.GetType().Name);
			}
			return result;
		}
		private static object quoteSign = Symbol.Intern("quote");
		public void Write(object t) {
			int ignore = -1;
			Write(t, ref ignore);
		}
		public void Write(object t, ref int maxElements) {
			if (maxElements==0) {
				stream.Write("...");
				return;
			} else {
				maxElements--;

				if (t==null)
					stream.Write("null");
				else {
					Type type = t.GetType();

					switch(Type.GetTypeCode(type)) {
						case System.TypeCode.Double:
							Double d = (double)t;
							if (Double.IsInfinity(d)) {
								if (Double.IsPositiveInfinity(d))
									stream.Write("\u221e");
								else
									stream.Write("\u2212\u221e");
							} else {
								stream.Write(d);
								if (d==(int)d)
									stream.Write(".0");
							}
							break;

						case System.TypeCode.Single:
							float f = (float)t;
							if (Single.IsInfinity(f)) {
								if (Single.IsPositiveInfinity(f))
									stream.Write("\u221e");
								else
									stream.Write("\u2212\u221e");
							} else {
								stream.Write(f);
								if (f==(int)f)
									stream.Write(".0");
							}
							break;

						case System.TypeCode.Byte:
						case System.TypeCode.Decimal:
						case System.TypeCode.Int16:
						case System.TypeCode.Int32:
						case System.TypeCode.Int64:
						case System.TypeCode.SByte:
						case System.TypeCode.UInt16:
						case System.TypeCode.UInt32:
						case System.TypeCode.UInt64:
						case System.TypeCode.Boolean:
							stream.Write(t);
							break;

						case System.TypeCode.String:
							WriteString((String)t);
							break;

						default:
							WriteComplex(t, type, ref maxElements);
							break;
					}
				}
			}
		}

		void WriteString(string s) {
			stream.Write('"');
			for (int i=0; i<s.Length; i++) {
				char c=s[i];
				if (c==((char)(8220)) || c=='"' || c=='\\')
					stream.Write('\\');
				stream.Write(c);
			}
			stream.Write('"');
		}
			
		void WriteComplex(object t, Type type, ref int maxElements) {
			if (Utilities.IsRemote(t)) {
				stream.Write("\u2039Remote {0}\u203a", type);
				return;
			}
			object probe = discloserTable[type];
			Discloser discloser = (probe==null)?null:(probe as Discloser);

			if (discloser!=null) {
				IList disclosed=null;
				stream.Write("\u2039");
				stream.Write(type.Name);
				try {
					disclosed = discloser.Disclose(t);
				} catch {
				}
				if (disclosed==null)
					stream.Write("error while printing");
				else {
					int l = disclosed.Count;
					if (l>0) {
						stream.Write(' ');
						for (int di=0; di<l-1;di++) {
							Write(disclosed[di], ref maxElements);
							stream.Write(' ');
						}
						Write(disclosed[l-1], ref maxElements);
					}
				}
				stream.Write("\u203a");
			} else if (t is Symbol) {
				stream.Write(((Symbol)t).name);
			} else if (t is IList) {
				IList list = (IList) t;

				if (maxElements>=0 && list.Count>maxElements) {
					stream.Write("\u2039{0}, ", list);
					stream.Write("{0} elements\u203a", list.Count);
				} else {
					if (list.Count>1 && list[0]==quoteSign) {
						stream.Write('\'');
						Write(list[1], ref maxElements);
					} else if (list.Count==3 && list[0]==Symbol.sMember && list[2] is string) {
						Write(list[1], ref maxElements);
						stream.Write(".");
						stream.Write(list[2]);
					} else {
						stream.Write('[');
						bool firstFlag=true;
						foreach (object o in list) {
							if (!firstFlag)
								stream.Write(' ');
							Write(o, ref maxElements);
							firstFlag=false;
						}
						if (list.Count==0)
							// In Arial, the empty list looks like a square if you don't put a space in the middle.
							stream.Write(' ');
						stream.Write(']');
					}
				}
			} else if (t is Procedure) {
				Procedure p = (Procedure)t;
				if (p is UserProcedure) {
					stream.Write("\u2039Procedure ");
					WriteUserProcedureName((UserProcedure)p);
					stream.Write("\u203a");
				}else if (p.name==null)
					stream.Write("\u2039Procedure (unnamed)\u203a");
				else if (p.name is Symbol)
					stream.Write("\u2039Procedure {0}\u203a", ((Symbol)(p.name)).name);
				else
					stream.Write("\u2039Procedure {0}\u203a", p.name);
			} else if (t is IRichWriteable && isRichText)
				((IRichWriteable)t).RichWrite((SomewhatRichTextWriter)stream);
			else if (t is System.Drawing.Image && isRichText) {
				((SomewhatRichTextWriter)stream).Write((System.Drawing.Image)t);
			} else if (t is Expression) {
				Write(((Expression)t).Unparse(), ref maxElements);
			} else if (t is Exception || t is System.Windows.Forms.Control)
				stream.Write(t.GetType().Name);
			else if (t is Type && !(t is ProcedureType)) {
				stream.Write("\u2039Type ");
				stream.Write(((Type)t).Name);
				stream.Write("\u203a");
			} else if (t is System.Reflection.Missing)
				stream.Write("\u2039NoValue\u203a");
			else if (t is Meta.EvalStack.PseudoContinuation)
				stream.Write((Meta.EvalStack.PseudoContinuation)t);
			else if (t is Meta.CallRecord) {
				((CallRecord)t).Write(this, ref maxElements);
			} else if (t is StringBox)
				stream.Write(t.ToString());
			else {
				stream.Write("\u2039");
				stream.Write(t);
				stream.Write("\u203a");
			}
		}

		void WriteUserProcedureName(UserProcedure p) {
			if (p==null) {
				stream.Write("unnamed");
				return;
			}
			if (p.name==null)
				stream.Write("unnamed");
			else if (p.name is Symbol)
				stream.Write(((Symbol)(p.name)).name);
			else
				stream.Write(p.name);
			if (p.parentEnvironment!=null) {
				stream.Write(" in ");
				WriteUserProcedureName(p.parentEnvironment.closure as UserProcedure);
			}
		}

		public void NewLine() {
			stream.Write('\n');
		}

		public class Discloser {
			MemberInfo[] members;
			public Discloser(Type t, string[] members) {
				this.members = new MemberInfo[members.Length];
				for (int i=0; i<members.Length;i++) {
					MemberInfo[] m = t.GetMember(members[i]);
					if (m==null || m.Length==0)
						throw new MissingMemberException(t.Name, members[i]);
					if (!(m[0] is FieldInfo || m[0] is PropertyInfo))
						throw new Exception("Member "+members[i]+" is not a field or property");
					this.members[i] = m[0];
				}
			}

			public IList Disclose(object o) {
				object[] result = new object[members.Length];
				for (int i=0;i<members.Length; i++) {
					MemberInfo m = members[i];
					FieldInfo f = m as FieldInfo;
					PropertyInfo p = m as PropertyInfo;
					if (f!=null)
						result[i] = f.GetValue(o);
					else
						result[i] = p.GetValue(o, new object[0]);
				}
				return result;
			}
		}

		public static void SetDisclosure(Type t, string[] members) {
			discloserTable[t] = new Discloser(t, members);
		}
	}

	public class BetterStringWriter : System.IO.StringWriter {
		System.Text.StringBuilder stringBuilder;
		public BetterStringWriter(System.Text.StringBuilder sb) : base(sb) {
			stringBuilder = sb;
		}

		public override void Write(string s) {
			bool overflow = false;
			int remainingCapacity = (stringBuilder.MaxCapacity-stringBuilder.Length)-1;
			if (s.Length>remainingCapacity) {
				s = s.Substring(0, remainingCapacity);
				overflow = true;
			}
			base.Write (s);
			if (overflow)
				throw new ArgumentOutOfRangeException();
		}
	}
}
